package edu.hcmus.sow.service.security;

import java.util.List;

import javax.sql.DataSource;

import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.jdbc.JdbcMutableAclService;
import org.springframework.security.acls.jdbc.LookupStrategy;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.AlreadyExistsException;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;

import edu.hcmus.sow.domain.security.CustomPrincipalSid;
import edu.hcmus.sow.domain.security.GroupSid;

public class CustomJdbcMutableAclService extends JdbcMutableAclService {

   private String classIdentityQuery = "select LAST_INSERT_ID()";
   private String sidIdentityQuery = "select LAST_INSERT_ID()";
   private String insertSid = "insert into acl_sid (type, sid) values (?, ?)";
   private String selectSidPrimaryKey = "select id from acl_sid where type=? and sid=?";
   private String insertObjectIdentity = "insert into acl_object_identity "
         + "(object_id_class, object_id_identity, parent_object, owner_sid, entries_inheriting) "
         + "values (?, ?, null, ?, ?)";

   // ~ Constructors
   // ===================================================================================================
   public CustomJdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) {
      super(dataSource, lookupStrategy, aclCache);
      setSidIdentityQuery(sidIdentityQuery);
      setClassIdentityQuery(classIdentityQuery);
      setInsertObjectIdentitySql(insertObjectIdentity);
   }

   // ~ Methods
   // ========================================================================================================
   @Override
   public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException {
      Assert.notNull(objectIdentity, "Object Identity required");

      // Check this object identity hasn't already been persisted
      if (retrieveObjectIdentityPrimaryKey(objectIdentity) != null) {
         throw new AlreadyExistsException("Object identity '" + objectIdentity + "' already exists");
      }

      // Need to retrieve the current principal, in order to know who "owns" this ACL (can be
      // changed later on)
      Authentication auth = SecurityContextHolder.getContext().getAuthentication();
      CustomPrincipalSid sid = new CustomPrincipalSid(auth);

      // Create the acl_object_identity row
      createObjectIdentity(objectIdentity, sid);

      // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
      Acl acl = readAclById(objectIdentity);
      Assert.isInstanceOf(MutableAcl.class, acl, "MutableAcl should be been returned");

      return (MutableAcl) acl;
   }

   /**
    * Retrieves the primary key from acl_sid, creating a new row if needed and the allowCreate
    * property is true.
    * 
    * @param sid
    *           to find or create
    * @param allowCreate
    *           true if creation is permitted if not found
    * 
    * @return the primary key or null if not found
    * 
    * @throws IllegalArgumentException
    *            if the <tt>Sid</tt> is not a recognized implementation.
    */
   @Override
   protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) {
      Assert.notNull(sid, "Sid required");

      String sidName;
      String sidType;

      if (sid instanceof CustomPrincipalSid) {
         sidName = ((CustomPrincipalSid) sid).getPrincipal();
         sidType = "U";
      } else if (sid instanceof GroupSid) {
         sidName = ((GroupSid) sid).getGroup();
         sidType = "G";
      } else if (sid instanceof GrantedAuthoritySid) {
         sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority();
         sidType = "A";
      } else {
         throw new IllegalArgumentException("Unsupported implementation of Sid");
      }

      List<Long> sidIds = jdbcTemplate.queryForList(selectSidPrimaryKey, new Object[] { sidType, sidName }, Long.class);

      if (!sidIds.isEmpty()) {
         return sidIds.get(0);
      }

      if (allowCreate) {
         jdbcTemplate.update(insertSid, sidType, sidName);
         Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running");
         return new Long(jdbcTemplate.queryForLong(sidIdentityQuery));
      }

      return null;
   }

}
